using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace Woz
{
    /// <summary>
    /// ink space converter class to convert between ink space and pixel coordniates
    /// </summary>
    public class InkSpaceConverter
    {
        /// <summary>
        /// private constructor means there should not be a single instance of this class
        /// </summary>
        private InkSpaceConverter()
        {
            // initialize the ink space converter
            using (Bitmap bmp = new Bitmap(1, 1))
            {
                using (Graphics g = this.InitializeInkGraphics(Graphics.FromImage(bmp)))
                {
                    // save the conversion rate from ink space to pixel
                    PointF[] points = new PointF[1] { new PointF(1.0f, 1.0f) };
                    g.TransformPoints(CoordinateSpace.Page, CoordinateSpace.Device, points);

                    // set the conversion rate
                    this.mConversionRate = points[0];
                }
            }

            // initialize the canvas rectangle
            this.mInkSpaceCanvasRectangle = this.PixelToInkSpace(new Rectangle(Point.Empty, Woz.StaticVariables.Instance.CanvasSize));

            // initialize two and ten pixels
            Point twoAndTenPixels = this.PixelToInkSpace(new Point(2, 10));
            this.mTwoPixels = twoAndTenPixels.X;
            this.mTenPixels = twoAndTenPixels.Y;
            this.mOnePixel = (int)((float)this.mTwoPixels / 2.0f + 0.5f);
            this.mFivePixels = (int)((float)this.mTenPixels / 2.0f + 0.5f);
        }

        /// <summary>
        /// gets the only instance of this singleton class
        /// </summary>
        public static InkSpaceConverter Instance
        {
            get
            {
                // if the instance doesnt exist
                if (sInstance == null)
                {
                    // create it
                    sInstance = new InkSpaceConverter();
                }

                // return it
                return sInstance;
            }
        }

        /// <summary>
        /// gets the conversion rate
        /// </summary>
        public PointF ConversionRate
        {
            get
            {
                // return the conversion rate
                return this.mConversionRate;
            }
        }

        /// <summary>
        /// gets the canvas rectangle in ink space coordinates
        /// </summary>
        public Rectangle InkSpaceCanvasRectangle
        {
            get
            {
                return this.mInkSpaceCanvasRectangle;
            }
        }

        /// <summary>
        /// one pixel in ink space
        /// </summary>
        public int InkSpaceOnePixel
        {
            get
            {
                return this.mOnePixel;
            }
        }

        /// <summary>
        /// two pixels in ink space
        /// </summary>
        public int InkSpaceTwoPixels
        {
            get
            {
                return this.mTwoPixels;
            }
        }

        /// <summary>
        /// five pixels in ink space
        /// </summary>
        public int InkSpaceFivePixels
        {
            get
            {
                return this.mFivePixels;
            }
        }

        /// <summary>
        /// ten pixels in ink space
        /// </summary>
        public int InkSpaceTenPixels
        {
            get
            {
                return this.mTenPixels;
            }
        }

        /// <summary>
        /// initialize the graphics to the ink space coordinates
        /// </summary>
        /// <param name="pGraphics">the graphics to initialize</param>
        /// <returns>graphics initialized to ink space coordinates</returns>
        public Graphics InitializeInkGraphics(Graphics pGraphics)
        {
            // set the graphics to a one hundred percent ink unit
            pGraphics.PageScale = 0.01f;
            pGraphics.PageUnit = GraphicsUnit.Millimeter;

            // return it
            return pGraphics;
        }

        /// <summary>
        /// converts a point in ink space to pixel coordinates
        /// </summary>
        /// <param name="pPoint">the point to convert</param>
        /// <returns>a new point in pixel coordniates</returns>
        public Point InkSpaceToPixel(Point pPoint)
        {
            // convert the x and y positions to pixel coordinate and return them
            return new Point(
                        (int)(pPoint.X / this.mConversionRate.X + 0.5f),
                        (int)(pPoint.Y / this.mConversionRate.Y + 0.5f));
        }

        /// <summary>
        /// converts an array of points in ink space to pixel coordinates
        /// </summary>
        /// <param name="pPoints">the array of points to convert</param>
        public void InkSpaceToPixel(Point[] pPoints)
        {
            // convert the x and y positions to pixel coordinate
            for (int i = 0; i < pPoints.Length; i++)
            {
                pPoints[i].X = (int)(pPoints[i].X / this.mConversionRate.X + 0.5f);
                pPoints[i].Y = (int)(pPoints[i].Y / this.mConversionRate.Y + 0.5f);
            }
        }

        /// <summary>
        /// converts an array of points in ink space to pixel coordinates
        /// </summary>
        /// <param name="pPoints">the array of points to convert</param>
        public void InkSpaceToPixel(PointF[] pPoints)
        {
            // convert the x and y positions to pixel coordinate
            for (int i = 0; i < pPoints.Length; i++)
            {
                pPoints[i].X = (pPoints[i].X / this.mConversionRate.X);
                pPoints[i].Y = (pPoints[i].Y / this.mConversionRate.Y);
            }
        }

        /// <summary>
        /// converts a rectangle in ink space to pixel coordinates
        /// </summary>
        /// <param name="pRectangle">the rectangle to convert</param>
        /// <returns>a rectangle in pixel coordniates</returns>
        public Rectangle InkSpaceToPixel(Rectangle pRectangle)
        {
            // convert the rectangle to pixel coordinate and return it
            return new Rectangle(
                        (int)(pRectangle.X / this.mConversionRate.X + 0.5f),
                        (int)(pRectangle.Y / this.mConversionRate.Y + 0.5f),
                        (int)(pRectangle.Width / this.mConversionRate.X + 0.5f),
                        (int)(pRectangle.Height / this.mConversionRate.Y + 0.5f));
        }

        /// <summary>
        /// converts a point in pixel coordinates to ink space
        /// </summary>
        /// <param name="pPoint">the point to convert</param>
        /// <returns>a point in ink space</returns>
        public Point PixelToInkSpace(Point pPoint)
        {
            // convert the x and y positions to ink space coordinate and return them
            return new Point(
                        (int)(pPoint.X * this.mConversionRate.X + 0.5f),
                        (int)(pPoint.Y * this.mConversionRate.Y + 0.5f));
        }

        /// <summary>
        /// converts an array of points in pixel coordinates to ink space
        /// </summary>
        /// <param name="pPoints">the array of points to convert</param>
        public void PixelToInkSpace(Point[] pPoints)
        {
            // convert the x and y positions to ink space coordinate
            for (int i = 0; i < pPoints.Length; i++)
            {
                pPoints[i].X = (int)(pPoints[i].X * this.mConversionRate.X + 0.5f);
                pPoints[i].Y = (int)(pPoints[i].Y * this.mConversionRate.Y + 0.5f);
            }
        }

        /// <summary>
        /// converts an array of points in pixel coordinates to ink space
        /// </summary>
        /// <param name="pPoints">the array of points to convert</param>
        public void PixelToInkSpace(PointF[] pPoints)
        {
            // convert the x and y positions to ink space coordinate
            for (int i = 0; i < pPoints.Length; i++)
            {
                pPoints[i].X = (pPoints[i].X * this.mConversionRate.X);
                pPoints[i].Y = (pPoints[i].Y * this.mConversionRate.Y);
            }
        }

        /// <summary>
        /// converts a rectangle in pixel coordinates to ink space
        /// </summary>
        /// <param name="pRectangle">the rectangle to convert</param>
        /// <returns>a rectangle in ink space</returns>
        public Rectangle PixelToInkSpace(Rectangle pRectangle)
        {
            // convert the rectangle to ink space coordinate and return it
            return new Rectangle(
                        (int)(pRectangle.X * this.mConversionRate.X + 0.5f),
                        (int)(pRectangle.Y * this.mConversionRate.Y + 0.5f),
                        (int)(pRectangle.Width * this.mConversionRate.X + 0.5f),
                        (int)(pRectangle.Height * this.mConversionRate.Y + 0.5f));
        }

        /// <summary>
        /// transforms physical pixel points on the screen to points on the canvas world coordinate
        /// the resulting points will be in the canvas world coordinate in ink space
        /// </summary>
        /// <remarks>this function assumes that your points are in pixel space coordinate</remarks>
        /// <example>if you want to convert length rather than position send the x y x+width y+height
        /// then you can simply subtract x+width-x or y+height-y to get the appropriate sizes</example>
        /// <param name="pPoints">an array of points in the physical pixel coordinate</param>
        public void ApplyTransformationToPixel(Point[] pPoints)
        {
            // first convert to ink space
            this.PixelToInkSpace(pPoints);

            // then transform using the inverse transformation matrix
            Woz.DynamicVariables.Instance.InverseTransformationMatrix.TransformPoints(pPoints);
        }

        /// <summary>
        /// transforms physical pixel points on the screen to points on the canvas world coordinate
        /// the resulting points will be in the canvas world coordinate in ink space
        /// </summary>
        /// <remarks>this function assumes that your points are in pixel space coordinate</remarks>
        /// <example>if you want to convert length rather than position send the x y x+width y+height
        /// then you can simply subtract x+width-x or y+height-y to get the appropriate sizes</example>
        /// <param name="pPoints">an array of points in the physical pixel coordinate</param>
        public void ApplyTransformationToPixel(PointF[] pPoints)
        {
            // first convert to ink space
            this.PixelToInkSpace(pPoints);

            // then transform using the inverse transformation matrix
            Woz.DynamicVariables.Instance.InverseTransformationMatrix.TransformPoints(pPoints);
        }

        /// <summary>
        /// transforms a physical rectangle on the screen to a rectangle on the canvas world coordinate
        /// the resulting rectangle will be in the canvas world coordinate in ink space
        /// </summary>
        /// <remarks>this function assumes that your rectangle is in pixel space coordinate</remarks>
        /// <example>this function converts length by sending the x y x+width y+height
        /// then simply subtracting x+width-x or y+height-y to get the appropriate sizes</example>
        /// <param name="pRectangle">a rectangle in the physical pixel coordinate</param>
        /// <returns>a rectangle in the canvas world coordinate</returns>
        public Rectangle ApplyTransformationToPixel(Rectangle pRectangle)
        {
            // convert
            Point[] pts = new Point[] { 
                new Point(pRectangle.X, pRectangle.Y), 
                new Point(pRectangle.X + pRectangle.Width, pRectangle.Y + pRectangle.Height) 
            };
            this.ApplyTransformationToPixel(pts);

            // return
            return new Rectangle(pts[0].X, pts[0].Y, pts[1].X - pts[0].X, pts[1].Y - pts[0].Y);
        }

        /// <summary>
        /// transforms points in the canvas world coordinates to physical pixel points on the screen
        /// the resulting points will be in physical coordinate in pixels
        /// </summary>
        /// <remarks>this function assumes that your points are in canvas world coordinates or ink space</remarks>
        /// <example>if you want to convert length rather than position send the x y x+width y+height
        /// then you can simply subtract x+width-x or y+height-y to get the appropriate sizes</example>
        /// <param name="pPoints">an array of points in the canvas world coordinate</param>
        public void UnapplyTransformationFromInk(Point[] pPoints)
        {
            // transform using the transformation matrix
            Woz.DynamicVariables.Instance.TransformationMatrix.TransformPoints(pPoints);

            // then convert to pixel
            this.InkSpaceToPixel(pPoints);
        }

        /// <summary>
        /// transforms points in the canvas world coordinates to physical pixel points on the screen
        /// the resulting points will be in physical coordinate in pixels
        /// </summary>
        /// <remarks>this function assumes that your points are in canvas world coordinates or ink space</remarks>
        /// <example>if you want to convert length rather than position send the x y x+width y+height
        /// then you can simply subtract x+width-x or y+height-y to get the appropriate sizes</example>
        /// <param name="pPoints">an array of points in the canvas world coordinate</param>
        public void UnapplyTransformationFromInk(PointF[] pPoints)
        {
            // transform using the transformation matrix
            Woz.DynamicVariables.Instance.TransformationMatrix.TransformPoints(pPoints);

            // then convert to pixel
            this.InkSpaceToPixel(pPoints);
        }

        /// <summary>
        /// transforms a rectangle in the canvas world coordinates to physical rectangle on the screen
        /// the resulting rectangle will be in physical coordinate in pixels
        /// </summary>
        /// <remarks>this function assumes that your rectangle is in canvas world coordinates or ink space</remarks>
        /// <example>this function converts length by sending the x y x+width y+height
        /// then simply subtracting x+width-x or y+height-y to get the appropriate sizes</example>
        /// <param name="pRectangle">a rectangle in the canvas world coordinate</param>
        /// <returns>a physical rectangle on the screen</returns>
        public Rectangle UnapplyTransformationFromInk(Rectangle pRectangle)
        {
            // convert
            Point[] pts = new Point[] { 
                new Point(pRectangle.X, pRectangle.Y), 
                new Point(pRectangle.X + pRectangle.Width, pRectangle.Y + pRectangle.Height) 
            };
            this.UnapplyTransformationFromInk(pts);

            // return
            return new Rectangle(pts[0].X, pts[0].Y, pts[1].X - pts[0].X, pts[1].Y - pts[0].Y);
        }

        /// <summary>
        /// the single instance of this class
        /// </summary>
        private static InkSpaceConverter sInstance;

        /// <summary>
        /// the conversion rate from ink to pixel
        /// </summary>
        private PointF mConversionRate;

        /// <summary>
        /// the canvas rectangle in ink space coordinates
        /// </summary>
        private Rectangle mInkSpaceCanvasRectangle;

        /// <summary>
        /// one pixel in ink space
        /// </summary>
        private int mOnePixel;

        /// <summary>
        /// two pixels in ink space
        /// </summary>
        private int mTwoPixels;
        
        /// <summary>
        /// five pixels in ink space
        /// </summary>
        private int mFivePixels;

        /// <summary>
        /// ten pixels in ink space
        /// </summary>
        private int mTenPixels;
    }
}
